/*
 *  Arrow
 *
 *  Created by David Giovannini on 1/5/07.
 *  Copyright 2007 Software by Jove. All rights reserved.
 *
 */
 
#ifndef SBJ_Stat_h
#define SBJ_Stat_h

#include <limits>

#include <sys/timeb.h>
#undef min
#undef max

namespace SBJ
{

/*
Class: Stat

Use the Stat Class to add the ability to rate the timing
of certain operations. It is very low overhead to use it.

But declaring an instance with calcSpans==true will add the overhead
of the system call ::ftime to every hit. This will give you
additional information about the time between hits.
*/

template < bool calcSpans = false >
class Stat
{
public:
	struct Result
	{
		Result( void )
		: seconds( 0.0 )
		, total( 0 )
		, rate( 0.0 )
		, minSpan( 0.0 )
		, maxSpan( 0.0 )
		, avgSpan( 0.0 )
		, medSpan( 0.0 ) {}

		Result( double secs, unsigned int tot, double r )
		: seconds( secs )
		, total( tot )
		, rate( r )
		, minSpan( 0.0 )
		, maxSpan( 0.0 )
		, avgSpan( 0.0 )
		, medSpan( 0.0 ) {}

		bool hasSpan( void ) const
		{
			return calcSpans;
		}

		double seconds;
		unsigned int total;
		double rate;
		double minSpan;
		double maxSpan;
		double avgSpan;
		double medSpan;
	};

	Stat( void )
	: m_total( 0 )
	, m_start( 0 )
	, m_stop( 0 )
	, m_current( 0 )
	, m_hit( 0 )
	, m_minSpan( calcSpans ? std::numeric_limits< unsigned int >::max() : 0 )
	, m_maxSpan( 0 )
	, m_sumSpan( 0 )
	{
	}

	~Stat( void ) {}

	bool hasSpan( void ) const
	{
		return calcSpans;
	}

	void start( void )
	{
		timeb t;
		::ftime( &t );
		m_start = m_current = static_cast< int >( t.millitm + (1000 * t.time) );
		m_total = 0;
	}

	void stop( void )
	{
		timeb t;
		::ftime( &t );
		m_stop = m_current = static_cast< int >( t.millitm + (1000 * t.time) );
	}

	void accume( void )
	{
		m_total++;
		if ( calcSpans )
		{
			timeb t;
			::ftime( &t );
			unsigned int newHit = static_cast< int >( t.millitm + (1000 * t.time) );
			if ( m_hit != 0 )
			{
				unsigned int span = (unsigned int)( newHit - m_hit );
				m_minSpan = (unsigned int)std::min( m_minSpan, span );
				m_maxSpan = (unsigned int)std::max( m_maxSpan, span );
				m_sumSpan += span;
			}
			m_hit = newHit;
		}
	}

	double getSeconds( void ) const
	{
		return ( (double)m_current - (double)m_start ) / 1000.0;
	}

	unsigned int getTotal( void ) const
	{
		return m_total;
	}

	double getRate( void ) const
	{
		return (double)getTotal() / getSeconds();
	}

	Result getResult( void ) const
	{
		if ( m_stop == 0 ) const_cast< Stat* >( this )->current();
		Result r( getSeconds(), getTotal(), getRate() );
		if ( calcSpans )
		{
			r.minSpan = (double)m_minSpan / 1000.0;
			r.maxSpan = (double)m_maxSpan / 1000.0;
			r.avgSpan = (double)m_sumSpan / (double)m_total / 1000.0;
			r.medSpan = (double)(m_maxSpan - m_minSpan) / 2.0 / 1000.0;
		}
		return r;
	}

private:
	void current( void )
	{
		timeb t;
		::ftime( &t );
		m_current = static_cast< int >( t.millitm + (1000 * t.time) );
	}

	unsigned int m_start;
	unsigned int m_stop;
	unsigned int m_current;
	unsigned int m_total;

	unsigned int m_hit;
	unsigned int m_minSpan;
	unsigned int m_maxSpan;
	unsigned int m_sumSpan;
};

}

#endif